# _*_ coding: utf-8 _*_

import numpy as np
from numpy import sqrt
from numpy import power
from random import uniform
from math import exp
from copy import deepcopy
import matplotlib.pyplot as plt

from scripts.utils.operators import Lifecycle
from scripts.utils.operators import History
from scripts.utils.operators import lhs_uniform

from data.CEC.CEC2017.cec17_functions import cec17_test_func


def func(x):
    res = 0
    for i in range(len(x) - 1):
        res += 100 * (x[i + 1] - x[i] ** 2) ** 2 + (x[i] - 1) ** 2
    return res


def euclidean_dis(a, b):
    return sqrt(np.nansum(power(a - b, 2)))


class IA:
    """
    Immune Algorithm (IA)
    """

    def __init__(self, func_num, dimension: int, bounds: tuple, NP: int, Pm: float, alpha: float):

        self.func_num = func_num
        self.__d, self.__bounds = dimension, bounds
        self.__NP, self.__Pm, self.__alpha = NP, Pm, alpha

        lhs = lhs_uniform(n=self.__NP, p=self.__d)
        x = (self.__bounds[0] + lhs * (self.__bounds[1] - self.__bounds[0]))  # variables
        self.__population = [[x[i], 0, 0, 0] for i in range(self.__NP)]  # [variables, fitness, density]
        self.__evaluate()
        for i, p in enumerate(self.__population):
            print(f'x[{i + 1}]: ', end='')
            print(p[0].tolist())
        print('-----------------------------------------')
        self.gBest = min(self.__population, key=lambda _: _[1])[1]
        self.life = Lifecycle()
        self.history = History(pieces=self.__NP)

    def optimize(self, iterations=None, evaluations=None, deadline=None):
        self.life.set_stopping_condition(iterations=iterations, evaluations=evaluations, deadline=deadline)

        while self.life.check_stopping_condition() is False:
            self.__population = self.__density(self.__population)
            self.__population = self.__expectation(self.__population)
            # for i, p in enumerate(self.__population):
            #     print(f'x[{i + 1}], affinity: {p[1]}, density: {p[2]}, expectation: {p[3]}')
            # print('-----------------------------------------')
            self.__immune_operation()
            # print('-----------------------------------------')
            #
            # for i, p in enumerate(self.__population):
            #     print(f'x[{i + 1}], affinity: {p[1]}, density: {p[2]}, expectation: {p[3]}')
            # print('-----------------------------------------')
            print( min(self.__population, key=lambda _: _[1])[1])
            if self.gBest > min(self.__population, key=lambda _: _[1])[1]:
                self.gBest = min(self.__population, key=lambda _: _[1])[1]

            self.history.update(gBest=self.gBest)
            self.life.update(it=1)

            plt.cla()
            plt.plot(range(len(self.history.gBest)), self.history.gBest)
            plt.pause(0.01)

        return self.history

    def __evaluate(self):
        f = [0]
        for i, x in enumerate(self.__population):
            # cec17_test_func(x=self.__population[i][0], f=f, nx=self.__d, mx=1, func_num=self.func_num)
            # self.__population[i][1] = f[0]
            self.__population[i][1] = func(self.__population[i][0])

    def __density(self, population):
        delta = 1 / (len(population) - 1)
        for i, _ in enumerate(population):
            similarity = 0
            euc_dis = []
            for p in population:
                euc_dis.append(euclidean_dis(population[i][0], p[0]))
            dis_sum = sum(euc_dis)
            for dis in euc_dis:
                sim = dis / dis_sum
                if sim < delta:
                    similarity += 1
            population[i][2] = similarity / len(population)
        return population

    def __expectation(self, population):
        for i, _ in enumerate(population):
            population[i][3] = population[i][1] * exp(-self.__alpha * population[i][2])
        return population

    def __immune_operation(self):
        self.__population = sorted(self.__population, key=lambda _: _[3])
        clones = []
        for i in range(3):
            # print(self.__population[i][0].tolist())
            for j in range(3):
                clone = deepcopy(self.__population[i])
                clones.append(clone)
        # print('-----------------------------------------')
        # for clone in clones:
        #     print(clone[0].tolist())

        disturb = (self.__bounds[1] - self.__bounds[0]) * 0.1
        for i, _ in enumerate(clones):
            for d in range(len(clones[i][0])):
                if uniform(0, 1) < self.__Pm:
                    clones[i][0][d] = clones[i][0][d] + (uniform(0, 1) - 0.5) * disturb
                    if clones[i][0][d] < self.__bounds[0]:
                        clones[i][0][d] = self.__bounds[0]
                    if clones[i][0][d] > self.__bounds[1]:
                        clones[i][0][d] = self.__bounds[1]

        # print('-----------------------------------------')
        clones = self.__density(clones)
        clones = self.__expectation(clones)

        # for i, clone in enumerate(clones):
        #     print(clone[0].tolist(), end=', ')
        #     print(f'clone[{i + 1}], affinity: {clone[1]}, density: {clone[2]}, expectation: {clone[3]}')
        clones = sorted(clones, key=lambda _: _[3])
        for i in range(3):
            self.__population[-1 - i] = deepcopy(clones[i])


ia = IA(func_num=1, dimension=4, bounds=(-30, 30), NP=5, Pm=0.2, alpha=1.0)
ia.optimize()
